{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Installation\n",
    "! pip install smolagents\n",
    "# To install from source instead of the last release, comment the command above and uncomment the following one.\n",
    "# ! pip install git+https://github.com/huggingface/smolagents.git"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 工具"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在这里，我们将学习高级工具的使用。\n",
    "\n",
    "> [!TIP]\n",
    "> 如果你是构建 agent 的新手，请确保先阅读 [agent 介绍](https://huggingface.co/docs/smolagents/main/zh/tutorials/../conceptual_guides/intro_agents) 和 [smolagents 导览](https://huggingface.co/docs/smolagents/main/zh/tutorials/../guided_tour)。\n",
    "\n",
    "- [工具](#工具)\n",
    "    - [什么是工具，如何构建一个工具？](#什么是工具如何构建一个工具)\n",
    "    - [将你的工具分享到 Hub](#将你的工具分享到-hub)\n",
    "    - [将 Space 导入为工具](#将-space-导入为工具)\n",
    "    - [使用 LangChain 工具](#使用-langchain-工具)\n",
    "    - [管理你的 agent 工具箱](#管理你的-agent-工具箱)\n",
    "    - [使用工具集合](#使用工具集合)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 什么是工具，如何构建一个工具？"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "工具主要是 LLM 可以在 agent 系统中使用的函数。\n",
    "\n",
    "但要使用它，LLM 需要被提供一个 API：名称、工具描述、输入类型和描述、输出类型。\n",
    "\n",
    "所以它不能仅仅是一个函数。它应该是一个类。\n",
    "\n",
    "因此，核心上，工具是一个类，它包装了一个函数，并带有帮助 LLM 理解如何使用它的元数据。\n",
    "\n",
    "以下是它的结构："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from smolagents import Tool\n",
    "\n",
    "class HFModelDownloadsTool(Tool):\n",
    "    name = \"model_download_counter\"\n",
    "    description = \"\"\"\n",
    "    This is a tool that returns the most downloaded model of a given task on the Hugging Face Hub.\n",
    "    It returns the name of the checkpoint.\"\"\"\n",
    "    inputs = {\n",
    "        \"task\": {\n",
    "            \"type\": \"string\",\n",
    "            \"description\": \"the task category (such as text-classification, depth-estimation, etc)\",\n",
    "        }\n",
    "    }\n",
    "    output_type = \"string\"\n",
    "\n",
    "    def forward(self, task: str):\n",
    "        from huggingface_hub import list_models\n",
    "\n",
    "        model = next(iter(list_models(filter=task, sort=\"downloads\", direction=-1)))\n",
    "        return model.id\n",
    "\n",
    "model_downloads_tool = HFModelDownloadsTool()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "自定义工具继承 [Tool](https://huggingface.co/docs/smolagents/main/zh/reference/tools#smolagents.Tool) 以继承有用的方法。子类还定义了：\n",
    "- 一个属性 `name`，对应于工具本身的名称。名称通常描述工具的功能。由于代码返回任务中下载量最多的模型，我们将其命名为 `model_download_counter`。\n",
    "- 一个属性 `description`，用于填充 agent 的系统提示。\n",
    "- 一个 `inputs` 属性，它是一个带有键 `\"type\"` 和 `\"description\"` 的字典。它包含帮助 Python 解释器对输入做出明智选择的信息。\n",
    "- 一个 `output_type` 属性，指定输出类型。`inputs` 和 `output_type` 的类型应为 [Pydantic 格式](https://docs.pydantic.dev/latest/concepts/json_schema/#generating-json-schema)，它们可以是以下之一：`[\"string\", \"boolean\",\"integer\", \"number\", \"image\", \"audio\", \"array\", \"object\", \"any\", \"null\"]`。\n",
    "- 一个 `forward` 方法，包含要执行的推理代码。\n",
    "\n",
    "这就是它在 agent 中使用所需的全部内容！\n",
    "\n",
    "还有另一种构建工具的方法。在 [guided_tour](https://huggingface.co/docs/smolagents/main/zh/tutorials/../guided_tour) 中，我们使用 `@tool` 装饰器实现了一个工具。[tool()](https://huggingface.co/docs/smolagents/main/zh/reference/tools#smolagents.tool) 装饰器是定义简单工具的推荐方式，但有时你需要更多：在类中使用多个方法以获得更清晰的代码，或使用额外的类属性。\n",
    "\n",
    "在这种情况下，你可以通过如上所述继承 [Tool](https://huggingface.co/docs/smolagents/main/zh/reference/tools#smolagents.Tool) 来构建你的工具。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 将你的工具分享到 Hub"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "你可以通过调用 [push_to_hub()](https://huggingface.co/docs/smolagents/main/zh/reference/tools#smolagents.Tool.push_to_hub) 将你的自定义工具分享到 Hub。确保你已经在 Hub 上为其创建了一个仓库，并且使用的是具有读取权限的 token。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_downloads_tool.push_to_hub(\"{your_username}/hf-model-downloads\", token=\"<YOUR_HUGGINGFACEHUB_API_TOKEN>\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为了使推送到 Hub 正常工作，你的工具需要遵守一些规则：\n",
    "- 所有方法都是自包含的，例如使用来自其参数中的变量。\n",
    "- 根据上述要点，**所有导入应直接在工具的函数中定义**，否则在尝试使用 [save()](https://huggingface.co/docs/smolagents/main/zh/reference/tools#smolagents.Tool.save) 或 [push_to_hub()](https://huggingface.co/docs/smolagents/main/zh/reference/tools#smolagents.Tool.push_to_hub) 调用你的自定义工具时会出现错误。\n",
    "- 如果你继承了 `__init__` 方法，除了 `self` 之外，你不能给它任何其他参数。这是因为在特定工具实例初始化期间设置的参数很难跟踪，这阻碍了将它们正确分享到 Hub。无论如何，创建特定类的想法是你已经可以为任何需要硬编码的内容设置类属性（只需在 `class YourTool(Tool):` 行下直接设置 `your_variable=(...)`）。当然，你仍然可以通过将内容分配给 `self.your_variable` 在代码中的任何地方创建类属性。\n",
    "\n",
    "一旦你的工具被推送到 Hub，你就可以查看它。[这里](https://huggingface.co/spaces/m-ric/hf-model-downloads) 是我推送的 `model_downloads_tool`。它有一个漂亮的 gradio 界面。\n",
    "\n",
    "在深入工具文件时，你可以发现所有工具的逻辑都在 [tool.py](https://huggingface.co/spaces/m-ric/hf-model-downloads/blob/main/tool.py) 下。这是你可以检查其他人分享的工具的地方。\n",
    "\n",
    "然后你可以使用 [load_tool()](https://huggingface.co/docs/smolagents/main/zh/reference/tools#smolagents.load_tool) 加载工具或使用 [from_hub()](https://huggingface.co/docs/smolagents/main/zh/reference/tools#smolagents.Tool.from_hub) 创建它，并将其传递给 agent 中的 `tools` 参数。\n",
    "由于运行工具意味着运行自定义代码，你需要确保你信任该仓库，因此我们需要传递 `trust_remote_code=True` 来从 Hub 加载工具。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from smolagents import load_tool, CodeAgent\n",
    "\n",
    "model_download_tool = load_tool(\n",
    "    \"{your_username}/hf-model-downloads\",\n",
    "    trust_remote_code=True\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 将 Space 导入为工具"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "你可以使用 [Tool.from_space()](https://huggingface.co/docs/smolagents/main/zh/reference/tools#smolagents.Tool.from_space) 方法直接从 Hub 导入一个 Space 作为工具！\n",
    "\n",
    "你只需要提供 Hub 上 Space 的 id、它的名称和一个帮助你的 agent 理解工具功能的描述。在底层，这将使用 [`gradio-client`](https://pypi.org/project/gradio-client/) 库来调用 Space。\n",
    "\n",
    "例如，让我们从 Hub 导入 [FLUX.1-dev](https://huggingface.co/black-forest-labs/FLUX.1-dev) Space 并使用它生成一张图片。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "image_generation_tool = Tool.from_space(\n",
    "    \"black-forest-labs/FLUX.1-schnell\",\n",
    "    name=\"image_generator\",\n",
    "    description=\"Generate an image from a prompt\"\n",
    ")\n",
    "\n",
    "image_generation_tool(\"A sunny beach\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "瞧，这是你的图片！🏖️\n",
    "\n",
    "<img src=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/sunny_beach.webp\">\n",
    "\n",
    "然后你可以像使用任何其他工具一样使用这个工具。例如，让我们改进提示 `A rabbit wearing a space suit` 并生成它的图片。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from smolagents import CodeAgent, InferenceClientModel\n",
    "\n",
    "model = InferenceClientModel(model_id=\"Qwen/Qwen3-Next-80B-A3B-Thinking\")\n",
    "agent = CodeAgent(tools=[image_generation_tool], model=model)\n",
    "\n",
    "agent.run(\n",
    "    \"Improve this prompt, then generate an image of it.\", additional_args={'user_prompt': 'A rabbit wearing a space suit'}\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```text\n",
    "=== Agent thoughts:\n",
    "improved_prompt could be \"A bright blue space suit wearing rabbit, on the surface of the moon, under a bright orange sunset, with the Earth visible in the background\"\n",
    "\n",
    "Now that I have improved the prompt, I can use the image generator tool to generate an image based on this prompt.\n",
    ">>> Agent is executing the code below:\n",
    "image = image_generator(prompt=\"A bright blue space suit wearing rabbit, on the surface of the moon, under a bright orange sunset, with the Earth visible in the background\")\n",
    "final_answer(image)\n",
    "```\n",
    "\n",
    "<img src=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/rabbit_spacesuit_flux.webp\">\n",
    "\n",
    "这得有多酷？🤩"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 使用 LangChain 工具"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们喜欢 Langchain，并认为它有一套非常吸引人的工具。\n",
    "要从 LangChain 导入工具，请使用 `from_langchain()` 方法。\n",
    "\n",
    "以下是如何使用它来重现介绍中的搜索结果，使用 LangChain 的 web 搜索工具。\n",
    "这个工具需要 `pip install langchain google-search-results -q` 才能正常工作。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.agents import load_tools\n",
    "\n",
    "search_tool = Tool.from_langchain(load_tools([\"serpapi\"])[0])\n",
    "\n",
    "agent = CodeAgent(tools=[search_tool], model=model)\n",
    "\n",
    "agent.run(\"How many more blocks (also denoted as layers) are in BERT base encoder compared to the encoder from the architecture proposed in Attention is All You Need?\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 管理你的 agent 工具箱"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "你可以通过添加或替换工具来管理 agent 的工具箱。\n",
    "\n",
    "让我们将 `model_download_tool` 添加到一个仅使用默认工具箱初始化的现有 agent 中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from smolagents import InferenceClientModel\n",
    "\n",
    "model = InferenceClientModel(model_id=\"Qwen/Qwen3-Next-80B-A3B-Thinking\")\n",
    "\n",
    "agent = CodeAgent(tools=[], model=model, add_base_tools=True)\n",
    "agent.tools[model_download_tool.name] = model_download_tool"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在我们可以利用新工具："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "agent.run(\n",
    "    \"Can you give me the name of the model that has the most downloads in the 'text-to-video' task on the Hugging Face Hub but reverse the letters?\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> [!TIP]\n",
    "> 注意不要向 agent 添加太多工具：这可能会让较弱的 LLM 引擎不堪重负。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 使用工具集合"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "你可以通过使用 ToolCollection 对象来利用工具集合，使用你想要使用的集合的 slug。\n",
    "然后将它们作为列表传递给 agent 初始化，并开始使用它们！"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from smolagents import ToolCollection, CodeAgent\n",
    "\n",
    "image_tool_collection = ToolCollection.from_hub(\n",
    "    collection_slug=\"huggingface-tools/diffusion-tools-6630bb19a942c2306a2cdb6f\",\n",
    "    token=\"<YOUR_HUGGINGFACEHUB_API_TOKEN>\"\n",
    ")\n",
    "agent = CodeAgent(tools=[*image_tool_collection.tools], model=model, add_base_tools=True)\n",
    "\n",
    "agent.run(\"Please draw me a picture of rivers and lakes.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为了加快启动速度，工具仅在 agent 调用时加载。"
   ]
  }
 ],
 "metadata": {},
 "nbformat": 4,
 "nbformat_minor": 4
}
